<?php

/**
 * 高并发模式雪花算法
 * 适合单机器高并发场景，最大化单毫秒ID生成量
 * 结构：1位符号位 + 41位时间戳 + 22位序列号
 * 支持约69年有效期，单毫秒最多生成4,194,304个ID
 * 注：主要代码由豆包AI生成
 */
class HighConcurrencySnowflake {

    // -------------------------------------------------------------------------

    // 起始时间戳（2020-01-01 00:00:00）
    const EPOCH = 1577836800000;
    // 时间戳位数
    const TIMESTAMP_BITS = 41;
    // 序列号位数（最大化单毫秒生成量）
    const SEQUENCE_BITS = 22;
    
    // 时间戳左移位数
    const TIMESTAMP_LEFT_SHIFT = self::SEQUENCE_BITS;
    // 序列号掩码
    const SEQUENCE_MASK = -1 ^ (-1 << self::SEQUENCE_BITS);
    
    private $sequence = 0;             // 当前序列号
    private $lastTimestamp = -1;       // 上一次生成ID的时间戳

    // -------------------------------------------------------------------------

    /**
     * 生成下一个唯一ID
     * @return string
     * @throws Exception
     */
    public function nextId(): string
    {
        $timestamp = $this->getCurrentTimestamp();
        
        // 处理时钟回拨
        if ($timestamp < $this->lastTimestamp) {
            $diff = $this->lastTimestamp - $timestamp;
            throw new Exception("系统时钟回拨，拒绝生成ID，回拨毫秒数: {$diff}");
        }
        
        // 同一毫秒内序列号递增
        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & self::SEQUENCE_MASK;
            
            // 序列号溢出，等待下一毫秒
            if ($this->sequence === 0) {
                $timestamp = $this->tilNextMillis($this->lastTimestamp);
            }
        } else {
            // 不同毫秒重置序列号
            $this->sequence = 0;
        }
        
        $this->lastTimestamp = $timestamp;
        
        // 组合生成ID
        $id = (
            (($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT) |
            $this->sequence
        );
        
        // 返回字符串避免大整数溢出
        return (string)$id;
    }

    // -------------------------------------------------------------------------

    /**
     * 获取当前毫秒级时间戳
     * @return int
     */
    private function getCurrentTimestamp(): int
    {
        return (int)(microtime(true) * 1000);
    }

    // -------------------------------------------------------------------------

    /**
     * 等待到下一毫秒
     * @param int $lastTimestamp
     * @return int
     */
    private function tilNextMillis(int $lastTimestamp): int
    {
        $timestamp = $this->getCurrentTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getCurrentTimestamp();
        }
        return $timestamp;
    }

    // -------------------------------------------------------------------------

}
